{*******************************************************************************
* The contents of this file are used with permission, subject to the Mozilla   *
* Public License Version 1.1 (the "License"); you may not use this file except *
* in compliance with the License. You may obtain a copy of the License at      *
* http://www.mozilla.org/MPL/                                                  *
*                                                                              *
* Software distributed under the License is distributed on an "AS IS" basis,   *
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for *
* the specific language governing rights and limitations under the License.    *
*                                                                              *
* The Original Code is distributed as part of the "mediate" product and is     *
* Copyright (C) @home media limited. All Rights Reserved.                      *
*                                                                              *
* Email: support@athomemedia.co.uk                                             *
* Web:   http://www.athomemedia.co.uk                                          *
*******************************************************************************}

unit Places;

interface

uses Classes, UIObjects, Functions, XMLHelper, AHMTypes;

{$IFDEF DESIGNER}
const
  // Property categories and hints for designer
  CAT_PLACE = 'Place';
  HINT_XPOS = 'defines the horizontal coordinate for this place item';
  HINT_YPOS = 'defines the vertical coordinate for this place item';
  HINT_XROT = 'defines rotation about the horizontal axis for this place item';
  HINT_YROT = 'defines rotation about the vertical axis for this place item';
  HINT_ZROT = 'defines rotation about the screen axis for this place item';
  HINT_VERTALIGN = 'controls vertical alignment in combination with Y coordinate';
  HINT_HORZALIGN = 'controls horizontal alignment in combination with X coordinate';
  HINT_VERTFLIP = 'specifies vertical flipping for the place without affecting position';
  HINT_HORZFLIP = 'specifies horizontal flipping for the place without affecting position';
{$ENDIF}

type
  TAHMVerticalAlignment = (vaNone, vaTop, vaBottom, vaCenter, vaParent);
  TAHMHorizontalAlignment = (haNone, haLeft, haRight, haCenter, haParent);

  TAHMPlace = class(TAHMUIObject)
  private
    FXPos, FYPos: Single;
    FXRot, FYRot, FZRot: Single;
    FXFlip, FYFlip: Boolean;
    FVirtX, FVirtY: Integer;
    FVertAlign: TAHMVerticalAlignment;
    FHorzAlign: TAHMHorizontalAlignment;
  protected
    function GetDesignerPos: String; override;
    procedure SetDesignerPos(Position: String); override;
    function GetDesignerRot: String; override;
    procedure SetDesignerRot(Rotation: String); override;
    procedure SetVirtualXPos(XPos: Integer);
    procedure SetVirtualYPos(YPos: Integer);
    procedure SetXRotation(X: Single);
    procedure SetYRotation(Y: Single);
    procedure SetZRotation(Z: Single);
    procedure SetVertAlign(Align: TAHMVerticalAlignment);
    procedure SetHorzAlign(Align: TAHMHorizontalAlignment);
    procedure SetFlipX(Flip: Boolean);
    procedure SetFlipY(Flip: Boolean);
    procedure ClearProperties; override;
  public
    constructor Create(const Name: String); override;
    property XPos: Single read FXPos write FXPos;
    property YPos: Single read FYPos write FYPos;
    function DesignerAdjustPos(DX, DY: Integer): Boolean; override;
    function DesignerAdjustRot(DX, DY, DZ: Single): Boolean; override;
    function RenderPreview: Boolean; override;
    function PreviewRect: TAHMRectF;
    procedure ApplyPosition(var X, Y, W, H: Single; ParentW, ParentH: Single);
    procedure Assign(Source: TPersistent); override;
    procedure SaveXMLBody(Lines: TStrings); override;
    procedure ParseFromXML(XML: TAHMXMLHelper); override;
    procedure BuildProperties(Properties: TAHMUIPropertyList); override;
  published
    property VirtualXPos: Integer read FVirtX write SetVirtualXPos;
    property VirtualYPos: Integer read FVirtY write SetVirtualYPos;
    property RotationX: Single read FXRot write SetXRotation;
    property RotationY: Single read FYRot write SetYRotation;
    property RotationZ: Single read FZRot write SetZRotation;
    property VerticalAlign: TAHMVerticalAlignment read FVertAlign write SetVertAlign;
    property HorizontalAlign: TAHMHorizontalAlignment read FHorzAlign write SetHorzAlign;
    property VerticalFlip: Boolean read FYFlip write SetFlipY;
    property HorizontalFlip: Boolean read FXFlip write SetFlipX;
  end;

  TAHMPlaceList = class(TAHMUIObjectList)
  private
  protected
  public
    constructor Create; override;
    function AddPlace(const InheritFrom, NewName: String): TAHMPlace;
    function GetPlace(const Name: String): TAHMPlace;
  end;


implementation

uses SysUtils, ApplicationManager, Screen, Canvas, Colour, TagNames, XMLTags,
     ErrorCodes, Utilities;


constructor TAHMPlaceList.Create;
begin
  inherited Create;

  XMLTagName := TAG_PLACES;
  ChildClass := TAHMPlace;
end;

function TAHMPlaceList.AddPlace(const InheritFrom, NewName: String): TAHMPlace;
begin
  Result := TAHMPlace(Inherit(InheritFrom, NewName));
end;

function TAHMPlaceList.GetPlace(const Name: String): TAHMPlace;
begin
  Result := TAHMPlace(GetObject(Name));
end;


//----------------------------------------------------------------------------//
constructor TAHMPlace.Create(const Name: String);
begin
  inherited Create(Name);

  XMLTagName := TAG_PLACE;
  ObjectType := otPlaces;
  ImageIndex := Ord(otPlaces);
end;

function TAHMPlace.GetDesignerPos: String;
begin
  // Return current X/Y position in concatenated string format
  Result := Format('%d,%d', [VirtualXPos, VirtualYPos]);
end;

procedure TAHMPlace.SetDesignerPos(Position: String);
var
  X, Y: Integer;
begin
  // Parse concatenated string format to derive new X/Y position
  if ParseStringValues(Position, X, Y) then
  begin
    SetVirtualXPos(X);
    SetVirtualYPos(Y);
  end;
end;

function TAHMPlace.GetDesignerRot: String;
begin
  // Return current X/Y/Z rotation in concatenated string format
  Result := Format('%f,%f,%f', [RotationX, RotationY, RotationZ]);
end;

procedure TAHMPlace.SetDesignerRot(Rotation: String);
var
  X, Y, Z: Single;
begin
  // Parse concatenated string format to derive new X/Y/Z rotation
  if ParseStringValues(Rotation, X, Y, Z) then
  begin
    SetXRotation(X);
    SetYRotation(Y);
    SetZRotation(Z);
  end;
end;

procedure TAHMPlace.SetVirtualXPos(XPos: Integer);
begin
  if FVirtX <> XPos then
    ApplicationManagerInstance.UIManager.InvalidateUIObject(Self);
  FVirtX := XPos;
  FXPos := ScreenInstance.ScaleVirtualX(XPos);
end;

procedure TAHMPlace.SetVirtualYPos(YPos: Integer);
begin
  if FVirtY <> YPos then
    ApplicationManagerInstance.UIManager.InvalidateUIObject(Self);
  FVirtY := YPos;
  FYPos := ScreenInstance.ScaleVirtualY(YPos);
end;

procedure TAHMPlace.SetXRotation(X: Single);
begin
  if FXRot <> X then
    ApplicationManagerInstance.UIManager.InvalidateUIObject(Self);
  FXRot := X;

  while FXRot > 360 do FXRot := FXRot - 360;
  while FXRot < 0 do FXRot := FXRot + 360;
end;

procedure TAHMPlace.SetYRotation(Y: Single);
begin
  if FYRot <> Y then
    ApplicationManagerInstance.UIManager.InvalidateUIObject(Self);
  FYRot := Y;

  while FYRot > 360 do FYRot := FYRot - 360;
  while FYRot < 0 do FYRot := FYRot + 360;
end;

procedure TAHMPlace.SetZRotation(Z: Single);
begin
  if FZRot <> Z then
    ApplicationManagerInstance.UIManager.InvalidateUIObject(Self);
  FZRot := Z;

  while FZRot > 360 do FZRot := FZRot - 360;
  while FZRot < 0 do FZRot := FZRot + 360;
end;

procedure TAHMPlace.SetVertAlign(Align: TAHMVerticalAlignment);
begin
  if FVertAlign <> Align then
    ApplicationManagerInstance.UIManager.InvalidateUIObject(Self);
  FVertAlign := Align;
end;

procedure TAHMPlace.SetHorzAlign(Align: TAHMHorizontalAlignment);
begin
  if FHorzAlign <> Align then
    ApplicationManagerInstance.UIManager.InvalidateUIObject(Self);
  FHorzAlign := Align;
end;

procedure TAHMPlace.SetFlipX(Flip: Boolean);
begin
  if Flip <> FXFlip then
    ApplicationManagerInstance.UIManager.InvalidateUIObject(Self);
  FXFlip := Flip;
end;

procedure TAHMPlace.SetFlipY(Flip: Boolean);
begin
  if Flip <> FYFlip then
    ApplicationManagerInstance.UIManager.InvalidateUIObject(Self);
  FYFlip := Flip;
end;

procedure TAHMPlace.ClearProperties;
begin
  inherited;

  // Clear custom place properties
  FXRot := 0.0;
  FYRot := 0.0;
  FZRot := 0.0;
  FXFlip := False;
  FYFlip := False;
  FVertAlign := vaNone;
  FHorzAlign := haNone;
  VirtualXPos := 0;
  VirtualYPos := 0;
end;

function TAHMPlace.DesignerAdjustPos(DX, DY: Integer): Boolean;
begin
  // Update position in response to designer adjustment
  VirtualXPos := VirtualXPos + DX;
  VirtualYPos := VirtualYPos + DY;
  if (DX <> 0) or (DY <> 0) then Modified := True;

  Result := False;
end;

function TAHMPlace.DesignerAdjustRot(DX, DY, DZ: Single): Boolean;
begin
  // Update rotation in response to designer adjustment
  RotationX := RotationX + DX;
  RotationY := RotationY + DY;
  RotationZ := RotationZ + DZ;
  if (DX <> 0) or (DY <> 0) or (DZ <> 0) then Modified := True;

  Result := False;
end;

function TAHMPlace.RenderPreview: Boolean;
{$IFDEF DESIGNER}
var
  Rect: TAHMRectF;
{$ENDIF}
begin
{$IFDEF DESIGNER}
  with ScreenInstance.Viewport do
  begin
    Canvas.DesignerBackground(ViewportRect);
    Canvas.SetRotation(FXRot, FYRot, FZRot, FXPos, FYPos);
    Canvas.BrushColour := TAHMCanvasColour.Highlight;
    Canvas.PenColour := TAHMCanvasColour.Black;
    Canvas.PenWidth := 0.001;

    // Calculate dimensions for preview rectangle
    Rect := PreviewRect;

    // Render crosshair to indicate coordinates
    Canvas.FillRect(Rect);
    Canvas.Line(MakeRectF(Rect.X, 0, 0, Height), nil);
    Canvas.Line(MakeRectF(0, Rect.Y, Width, 0), nil);
  end;
{$ENDIF}
  Result := True;
end;

function TAHMPlace.PreviewRect: TAHMRectF;
var
  X, Y, BoxW, BoxH: Single;
begin
  with ScreenInstance.Viewport do
  begin
    // Apply alignment and parent calculation to origin
    X := FXPos; Y := FYPos; BoxW := Width / 5; BoxH := Height / 5;
    ApplyPosition(X, Y, BoxW, BoxH, Width, Height);

    Result := MakeRectF(X, Y, BoxW, BoxH);
  end;
end;

procedure TAHMPlace.ApplyPosition(var X, Y, W, H: Single; ParentW, ParentH: Single);
begin
  // Determine vertical alignment
  case FVertAlign of
    vaNone, vaTop:
      Y := FYPos;
    vaBottom:
      Y := ParentH - H - FYPos;
    vaCenter:
      Y := ((ParentH - H) / 2) + FYPos;
    vaParent:
    begin
      Y := 0;
      H := ParentH;
    end;
  end;

  // Determine horizontal alignment
  case FHorzAlign of
    haNone, haLeft:
      X := FXPos;
    haRight:
      X := ParentW - W - FXPos;
    haCenter:
      X := ((ParentW - W) / 2) + FXPos;
    haParent:
    begin
      X := 0;
      W := ParentW;
    end;
  end;
end;

procedure TAHMPlace.Assign(Source: TPersistent);
begin
  inherited Assign(Source);

  with Source as TAHMPlace do
  begin
    Self.VirtualXPos := VirtualXPos;
    Self.VirtualYPos := VirtualYPos;
    Self.RotationX := RotationX;
    Self.RotationY := RotationY;
    Self.RotationZ := RotationZ;
    Self.VerticalAlign := VerticalAlign;
    Self.HorizontalAlign := HorizontalAlign;
    Self.HorizontalFlip := HorizontalFlip;
    Self.VerticalFlip := VerticalFlip;
  end;
end;

procedure TAHMPlace.SaveXMLBody(Lines: TStrings);
var
  oAncs: TAHMPlace;
  sWork: String;
begin
  // Determine which ancestor to compare against - real or empty
  oAncs := TAHMPlace(ObjectToCompare);

  // Do we need to store position?
  if ((FVirtX <> 0) and (FVirtX <> oAncs.VirtualXPos)) or
     ((FVirtY <> 0) and (FVirtY <> oAncs.VirtualYPos)) then
    Lines.Add(TABS[3] + TGB+TAG_POSITION+TGC+ IntToStr(FVirtX) + ',' +
              IntToStr(FVirtY) + TGE+TAG_POSITION+TGC);

  // Do we need to store rotation?
  if ((FXRot <> 0) and (FXRot <> oAncs.RotationX)) or
     ((FYRot <> 0) and (FYRot <> oAncs.RotationY)) or
     ((FZRot <> 0) and (FZRot <> oAncs.RotationZ)) then
    Lines.Add(TABS[3] + TGB+TAG_ROTATION+TGC + Float2Str(FXRot) + ',' +
              Float2Str(FYRot) + ',' + Float2Str(FZRot) + TGE+TAG_ROTATION+TGC);

  // Do we need to store flipping?
  if (FXFlip or (FXFlip <> oAncs.HorizontalFlip)) or
     (FYFlip or (FYFlip <> oAncs.VerticalFlip)) then
  begin
    sWork := TGB+TAG_FLIP;
    if (FYFlip or (FYFlip <> oAncs.VerticalFlip)) then
      sWork := sWork + SPC+ATTR_VERT+ATB + Bool2Str(FYFlip) + ATQ;
    if (FXFlip or (FXFlip <> oAncs.HorizontalFlip)) then
      sWork := sWork + SPC+ATTR_HORIZ+ATB + Bool2Str(FXFlip) + ATQ;
    Lines.Add(TABS[3] + sWork + ETE);
  end;

  // Do we need to store alignment?
  if ((FVertAlign <> vaNone) and (FVertAlign <> oAncs.VerticalAlign)) or
     ((FHorzAlign <> haNone) and (FHorzAlign <> oAncs.HorizontalAlign)) then
  begin
    sWork := TGB+TAG_ALIGN;
    if (FVertAlign <> vaNone) and (FVertAlign <> oAncs.VerticalAlign) then
    begin
      sWork := sWork + SPC+ATTR_VERT+ATB;
      if FVertAlign = vaTop then sWork := sWork + ATTR_VAL_TOP
      else if FVertAlign = vaBottom then sWork := sWork + ATTR_VAL_BOTTOM
      else if FVertAlign = vaCenter then sWork := sWork + ATTR_VAL_CENTER
      else sWork := sWork + ATTR_VAL_PARENT;
      sWork := sWork + ATQ;
    end;
    if (FHorzAlign <> haNone) and (FHorzAlign <> oAncs.HorizontalAlign) then
    begin
      sWork := sWork + SPC+ATTR_HORIZ+ATB;
      if FHorzAlign = haLeft then sWork := sWork + ATTR_VAL_LEFT
      else if FHorzAlign = haRight then sWork := sWork + ATTR_VAL_RIGHT
      else if FHorzAlign = haCenter then sWork := sWork + ATTR_VAL_CENTER
      else sWork := sWork + ATTR_VAL_PARENT;
      sWork := sWork + ATQ;
    end;
    Lines.Add(TABS[3] + sWork + ETE);
  end;
end;

procedure TAHMPlace.ParseFromXML(XML: TAHMXMLHelper);
var
  sVert, sHorz: String;
  iX, iY: Integer;
  PlaceNode, PropsNode: TAHMXMLNode;
begin
  // Initialise parsing nodes
  PlaceNode := XML.CurrentNode;
  PropsNode := nil;

  // All place elements are optional, so see what we have
  while XML.NextElement(PlaceNode, PropsNode) do
    if XML.CurrentElement = TAG_POSITION then
    begin
      if not XML.GetCoordsValue(iX, iY) then
        LogParsingError(THM_PRS_INT_MALFORMED_PL_POSITION_TAGS, Self);
      VirtualXPos := iX;
      VirtualYPos := iY;
    end
    else if XML.CurrentElement = TAG_ROTATION then
    begin
      if not XML.GetCoordsValue(FXRot, FYRot, FZRot) then
        LogParsingError(THM_PRS_INT_MALFORMED_PL_ROTATION_TAGS, Self);
    end
    else if XML.CurrentElement = TAG_FLIP then
    begin
      // Determine flip properties
      sVert := XML.GetAttribute(ATTR_VERT);
      sHorz := XML.GetAttribute(ATTR_HORIZ);

      if sVert <> '' then FYFlip := Str2Bool(sVert);
      if sHorz <> '' then FXFlip := Str2Bool(sHorz);
    end
    else if XML.CurrentElement = TAG_ALIGN then
    begin
      // Determine alignment properties
      sVert := XML.GetAttribute(ATTR_VERT);
      sHorz := XML.GetAttribute(ATTR_HORIZ);

      if sVert = ATTR_VAL_TOP then VerticalAlign := vaTop
      else if sVert = ATTR_VAL_BOTTOM then VerticalAlign := vaBottom
      else if sVert = ATTR_VAL_CENTER then VerticalAlign := vaCenter
      else if sVert = ATTR_VAL_PARENT then VerticalAlign := vaParent
      else if sVert = ATTR_VAL_NONE then VerticalAlign := vaNone
      else if sVert <> '' then
        LogParsingError(THM_PRS_INT_MALFORMED_PL_ALIGNMENT_TAGS, Self);

      if sHorz = ATTR_VAL_LEFT then HorizontalAlign := haLeft
      else if sHorz = ATTR_VAL_RIGHT then HorizontalAlign := haRight
      else if sHorz = ATTR_VAL_CENTER then HorizontalAlign := haCenter
      else if sHorz = ATTR_VAL_PARENT then HorizontalAlign := haParent
      else if sHorz = ATTR_VAL_NONE then HorizontalAlign := haNone
      else if sHorz <> '' then
        LogParsingError(THM_PRS_INT_MALFORMED_PL_ALIGNMENT_TAGS, Self);
    end;
end;

procedure TAHMPlace.BuildProperties(Properties: TAHMUIPropertyList);
begin
{$IFDEF DESIGNER}
  inherited;

  // Build custom place properties
  Properties.Add('Coordinate X', 'VirtualXPos', CAT_PLACE, HINT_XPOS);
  Properties.Add('Coordinate Y', 'VirtualYPos', CAT_PLACE, HINT_YPOS);
  Properties.Add('Rotation X', 'RotationX', CAT_PLACE, HINT_XROT);
  Properties.Add('Rotation Y', 'RotationY', CAT_PLACE, HINT_YROT);
  Properties.Add('Rotation Z', 'RotationZ', CAT_PLACE, HINT_ZROT);
  Properties.Add('Alignment X', 'HorizontalAlign', CAT_PLACE, HINT_HORZALIGN);
  Properties.Add('Alignment Y', 'VerticalAlign', CAT_PLACE, HINT_VERTALIGN);
  Properties.Add('Flip X', 'HorizontalFlip', CAT_PLACE, HINT_HORZFLIP);
  Properties.Add('Flip Y', 'VerticalFlip', CAT_PLACE, HINT_VERTFLIP);
{$ENDIF}
end;


end.
